package otSpaces.server.util;

import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.io.Serializable;
import java.util.HashMap;
import java.util.Map;
import java.util.Properties;
import java.util.Set;
import java.util.logging.Logger;

import com.sun.sgs.app.AppContext;
import com.sun.sgs.app.ManagedObjectRemoval;
import com.sun.sgs.app.ManagedReference;
import com.sun.sgs.app.util.ScalableHashMap;

/**
 * This is a mapping which can be used with ManagedObjects directly as values.
 * When a value is removed form the Data Store, the mapping will be
 * automatically removed. The object's constructor is used to specify whether or
 * not to remove values from the data store once the mapping has been removed.
 * 
 * 
 * The object can use a {@link HashMap} or a {@link ScalableHashMap} based on a
 * configuration default or the second (optional) constructor parameter.
 * 
 * 
 * @author Peter LaValle
 * 
 * @param <K>
 * @param <V>
 */
public class ReferenceMapping<K extends Serializable, V extends Serializable>
		implements ManagedObjectRemoval, Serializable {

	public static class Ref<K extends Serializable, V extends Serializable>
			extends AmbiguousReference<ReferenceMapping<K, V>> {

		private static final long serialVersionUID = -3888826366643346033L;

		public Ref(boolean markForRemoval) {
			super(new ReferenceMapping<K, V>(markForRemoval));
		}
	}

	public static final Logger LOG = Logger.getLogger(ReferenceMapping.class
			.getName());

	private static final long serialVersionUID = -6313481681007443750L;
	public static final boolean USE_SCALABLE;

	static {
		final Properties properties = new Properties(System.getProperties());

		try {
			properties.load(new FileInputStream(ReferenceMapping.class
					.getName() + ".ini"));
		} catch (FileNotFoundException e) {
			LOG.info("{" + ReferenceMapping.class.getName()
					+ "} has no configuration file and is using defaults");
		} catch (IOException e) {

			if (!(e instanceof FileNotFoundException)) {
				LOG.warning("Excpetion [" + e.getMessage()
						+ "] detected while trying to load .ini for {"
						+ ReferenceMapping.class.getName() + "}");
				e.printStackTrace();
			}
			LOG.info("{"
					+ ReferenceMapping.class.getName()
					+ "} has no configuration file and is using defaults (continuing happily ...)");
		}

		USE_SCALABLE = Boolean.parseBoolean(properties.getProperty(
				ReferenceMapping.class.getName() + ".USE_SCALABLE", "true"));
	}

	private final Map<K, AmbiguousReference<V>> _internalMap;
	private final ManagedReference<Map<K, AmbiguousReference<V>>> _internalMapRef;

	public final boolean _markForRemoval;

	public ReferenceMapping(boolean markForRemoval) {
		this(markForRemoval, USE_SCALABLE);
	}

	public ReferenceMapping(boolean markForRemoval, boolean useScalableMap) {
		_markForRemoval = markForRemoval;

		if (useScalableMap) {
			_internalMap = null;
			_internalMapRef = AppContext
					.getDataManager()
					.createReference(
							(Map<K, AmbiguousReference<V>>) new ScalableHashMap<K, AmbiguousReference<V>>());
		} else {
			_internalMap = new HashMap<K, AmbiguousReference<V>>();
			_internalMapRef = null;

		}
	}

	public void clear() {
		for (K key : keySet()) {
			remove(key);
		}
	}

	public boolean containsKey(Object key) {
		return internalMap().containsKey(key);
	}

	public V get(Object key) {
		return internalMap().containsKey(key) ? internalMap().get(key).get()
				: null;
	}

	private Map<K, AmbiguousReference<V>> internalMap() {
		return _internalMap == null ? _internalMapRef.get() : _internalMap;
	}

	public boolean isEmpty() {
		return internalMap().isEmpty();
	}

	public Set<K> keySet() {
		return internalMap().keySet();
	}

	public void put(K key, V value) {
		remove(key);

		internalMap().put(key, new AmbiguousReference<V>(value));

	}

	public void remove(Object key) {

		if (internalMap().containsKey(key) && _markForRemoval) {
			internalMap().get(key).markForRemoval();
		}

		internalMap().remove(key);
	}

	@Override
	public void removingObject() {
		clear();

		if (_internalMapRef != null) {
			AppContext.getDataManager().removeObject(_internalMapRef.get());
		}
	}

	public int size() {
		return internalMap().size();
	}
}
